/**
 *    Copyright 2009 Seth Albnaese
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.aquiferserver.server.net;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.aquiferserver.common.AquiferMessage;
import org.aquiferserver.common.HandshakeConfirmationMessage;
import org.aquiferserver.common.HandshakeMessage;
import org.aquiferserver.common.MessageType;
import org.aquiferserver.common.Player;
import org.aquiferserver.server.game.Game;
import org.aquiferserver.server.messaging.MessageHandlerFactory;
import org.aquiferserver.server.model.PlayerCommand;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelEvent;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelPipelineCoverage;
import org.jboss.netty.channel.ChannelStateEvent;
import org.jboss.netty.channel.ExceptionEvent;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.SimpleChannelHandler;
import org.jboss.netty.util.internal.MapBackedSet;

/**
 * @author Seth Albanese
 * @version $Id: AquiferServerHandler.java 60 2009-06-15 08:47:09Z salbanese0080@gmail.com $
 */

@ChannelPipelineCoverage( "all" )
public class AquiferServerHandler extends SimpleChannelHandler implements AquiferMessageBroker
{
	protected static final Log logger = LogFactory.getLog( org.aquiferserver.server.net.AquiferServerHandler.class );
	
	private static final Set<Channel> channels = new MapBackedSet<Channel>( new ConcurrentHashMap<Channel, Boolean>() );
	private static final ConcurrentHashMap<Integer, Channel> connectingChannels = new ConcurrentHashMap<Integer, Channel>();
	
	private MessageHandlerFactory messageHandlerFactory;
	private Game game;	
	
	public void setGame( Game g )
	{
		game = g;
	}
	
	public void setMessageHandlerFactory( MessageHandlerFactory factory )
	{
		this.messageHandlerFactory = factory;
	}
	
	/* (non-Javadoc)
	 * @see org.jboss.netty.channel.SimpleChannelHandler#handleUpstream(org.jboss.netty.channel.ChannelHandlerContext, org.jboss.netty.channel.ChannelEvent)
	 */
	@Override
	public void handleUpstream( ChannelHandlerContext ctx, ChannelEvent e ) throws Exception 
	{
		if ( e instanceof ChannelStateEvent )
		{
			logger.info ( e.toString() );
		}
		
		super.handleUpstream( ctx, e );
	}

	/* (non-Javadoc)
	 * @see org.jboss.netty.channel.SimpleChannelHandler#channelConnected(org.jboss.netty.channel.ChannelHandlerContext, org.jboss.netty.channel.ChannelStateEvent)
	 */
	@Override
	public void channelConnected( ChannelHandlerContext ctx, ChannelStateEvent e ) throws Exception 
	{
		logger.debug( "Recieved connection request from " + e.getChannel().getRemoteAddress() );
		
		HandshakeMessage msg = new HandshakeMessage();		
		msg.setClientId( e.getChannel().getId() );		
		msg.setSender( AquiferMessage.FROM_SERVER );
		
		/*
		if( game == null )
		{
			logger.fatal( "No game has been set" );
			throw new Exception( "Game is Undefined" );
		}
		else
		{			
			 msg.setPeers( ( Player[] ) game.getPlayers().toArray() );
		}
		*/
		msg.setPeers( new Player[0] );
		
		connectingChannels.put( e.getChannel().getId(), e.getChannel() );
		
		logger.debug( "Sending handshake message to " + msg.getClientId() );
		
		e.getChannel().write( msg );		
	}
	
	/* (non-Javadoc)
	 * @see org.jboss.netty.channel.SimpleChannelHandler#channelClosed(org.jboss.netty.channel.ChannelHandlerContext, org.jboss.netty.channel.ChannelStateEvent)
	 */
	@Override
	public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception 
	{
		logger.debug( "Removing channel " + e.getChannel().getId() );
		
		if( channels.contains( e.getChannel() ) )
		{
			channels.remove( e.getChannel() );
		}
		else if ( connectingChannels.contains( e.getChannel() ) )
		{
			connectingChannels.remove( e.getChannel() );
		}
		
		game.removePlayer( e.getChannel().getId() );	
		
	}

	/* (non-Javadoc)
	 * @see org.jboss.netty.channel.SimpleChannelHandler#messageReceived(org.jboss.netty.channel.ChannelHandlerContext, org.jboss.netty.channel.MessageEvent)
	 */
	@Override
	public void messageReceived( ChannelHandlerContext ctx, MessageEvent e ) throws Exception 
	{
		AquiferMessage request = ( AquiferMessage ) e.getMessage();
		request.setSender( e.getChannel().getId() );
		
		logger.debug( "MESSAGE RECIEVED [" + e.getChannel().getRemoteAddress() + "] " + request );
		
		/*
		MessageHandler handler = messageHandlerFactory.getHandler( request );
		handler.handleMessage( request );
		
				or
		
		AquiferCommand cmd = handler.handleMessage();
		cmd.execute()
		 */
		
		
		if( request.getType().equals( MessageType.MT_HANDSHAKE_CONFIRM ) 
				&& connectingChannels.containsKey( e.getChannel().getId() ) )
		{
			logger.debug( "Recieved handshake confirmation from " + e.getChannel().getId() );
			confirmHandshake( e.getChannel(), ( HandshakeConfirmationMessage ) request );
			return;
		}
				
		//game.addPlayerCommand( new PlayerCommand( e.getChannel().getId(), request.getData() ) );		
		
	}
	
	/* (non-Javadoc)
	 * @see org.aquiferserver.server.AquiferMessageBroker#broadcastMessage(org.aquiferserver.server.AquiferMessage)
	 */
	@Override
	public void broadcastMessage(AquiferMessage msg) {
		
		for ( Channel c: channels )
		{			
			c.write( msg ); 
		}
		
	}
	
	/* (non-Javadoc)
	 * @see org.aquiferserver.server.net.AquiferMessageBroker#broadcastMessage(org.aquiferserver.server.model.AquiferMessage, java.util.Set)
	 */
	@Override
	public void broadcastMessage(AquiferMessage msg, Set<Integer> recipiants ) 
	{
		for ( Channel c: channels )
		{
			if( recipiants.contains( c.getId() ) )
			{
				c.write( msg );
			}
		}
	}

	/* (non-Javadoc)
	 * @see org.jboss.netty.channel.SimpleChannelHandler#exceptionCaught(org.jboss.netty.channel.ChannelHandlerContext, org.jboss.netty.channel.ExceptionEvent)
	 */
	@Override
	public void exceptionCaught( ChannelHandlerContext ctx, ExceptionEvent e ) throws Exception 
	{
		logger.warn( "Unexpected exception from downstream.", e.getCause() );
		
		e.getChannel().close();
		channels.remove( e.getChannel() );
	}		
	
	private void confirmHandshake( Channel channel, HandshakeConfirmationMessage msg )
	{	
		channels.add( connectingChannels.remove( channel.getId() ) );
			
		
		logger.debug( msg.getPlayer().toString() );
		
		msg.getPlayer().setId( channel.getId() );
		
		//msg.setPlayer( game.addPlayer( msg.getPlayer() ) );
		
		msg.setSender( AquiferMessage.FROM_SERVER );
		
		channel.write( msg );
			
		//if( !game.isRunning() ) { game.start(); }
		
		logger.info( "Handshake confirmed for Channel " + channel.getId() );
	}
}





